package service

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"meeting-manager/internal/model"
	"meeting-manager/pkg/response"
	"net/http"
	"strconv"
	"strings"
	"time"
)

var meetingList *model.MeetingList

func init() {
	meetingList = model.NewMeetingList()

	go func() {
		for true {
			time.Sleep(3 * time.Second)

			fmt.Println("server list:")
			for _, server := range serverList.GetServers() {
				fmt.Println(server.GetName(), ":", server.GetAddr())
			}

			fmt.Println("meeting list:")
			for _, meeting := range meetingList.GetMeetings() {
				fmt.Println(meeting.Id, ":", meeting.Addr)
			}
		}
	}()
}

const (
	// 讨论会
	discussPattern = iota + 1
)

type CreateMeetingHandler struct{}

// 为会议分配服务器，并向用户返回会议信息
// 请求参数中需要包括：用户 id、会议模式、参会人数
// 目前计划应用的会议模式包括成员讨论模式和直播模式
// 在讨论模式下，每个用户会建立一条上行通道和 n-1 条下行通道，对服务器带宽要求较高
// 而在直播模式下，发起者会建立一条上行通道，接收者会建立一条下行通道，同样人数下对服务器要求较低
// 当用户发起创建会议的请求时，会议管理服务器会根据会议的承载人数和模式计算出相应的负载
// 然后遍历所有向本服务器注册的 sfu 服务器记录信息
// 根据选择策略挑选出最合适的 sfu 服务器创建会议信息 model.Meeting
// 会议信息中需包含随机生成且不重复的会议号和服务器的地址
func (handler *CreateMeetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var userId, limit, pattern int
	var err error

	// 用户 id
	userId, err = strconv.Atoi(r.PostFormValue("userId"))
	if err != nil {
		_, _ = w.Write(response.ParamErr.ToByte())
		return
	}

	// 参会人数
	limit, err = strconv.Atoi(r.PostFormValue("limit"))
	if err != nil || limit <= 0 {
		_, _ = w.Write(response.ParamErr.ToByte())
		return
	}

	// 会议模式
	pattern, err = strconv.Atoi(r.PostFormValue("pattern"))
	if err != nil {
		_, _ = w.Write(response.ParamErr.ToByte())
		return
	}

	// 计算负载
	load := calculateLoad(pattern, limit)

	// 寻找有没有合适的服务器
	addr, matchSuccess := serverList.MatchServer(load)

	// 如果没有找到，则进行相应的通知
	if !matchSuccess {
		_, _ = w.Write(response.CreateMeetingErr.ToByte())
		return
	}

	// 创建该会议
	meeting := meetingList.CreateAndAddMeeting(addr, limit)

	// 将该用户添加到会议的用户列表中
	_ = meeting.AddUser(model.NewUser(userId, model.Address(r.RemoteAddr)))

	// 向媒体服务器发起创建会议的请求内容
	createInfo := make(map[string]interface{})
	createInfo["meetingId"] = meeting.Id
	createInfo["limit"] = limit
	createInfo["pattern"] = pattern

	// 将 map 转换成 json 格式的字符数组
	body, err := json.Marshal(createInfo)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// 创建 bytes.Reader，并将请求体字符数组传入
	// 该结构体实现了 io.Reader 接口，可作为 http.Post() 的第三个参数
	reader := bytes.NewReader(body)

	// 在媒体服务器创建会议
	var resp *http.Response
	resp, err = http.Post("http://"+string(addr)+"/create",
		"application/json;charset=utf-8", reader)

	// 如果有错误发生，就要移除刚刚创建的会议
	if err != nil || resp.StatusCode != http.StatusOK {
		meetingList.RemoveMeetingById(meeting.Id)
		_, _ = w.Write(response.DisconnectServer.ToByte())
		return
	}

	// 将会议的地址和会议号返回给用户
	res := response.Success
	res.WithData(meeting)
	_, _ = w.Write(res.ToByte())
}

type JoinMeetingHandler struct{}

// 加入会议，根据会议号获取支持会议的服务器的地址
// 请求参数中需要包括：用户 id、会议号
// 会议管理服务器需要返回给用户会议所在的 sfu 服务器的地址
func (handler *JoinMeetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var userId, meetingId int
	var err error

	// 用户 id
	userId, err = strconv.Atoi(r.PostFormValue("userId"))
	if err != nil {
		_, _ = w.Write(response.ParamErr.ToByte())
		return
	}

	// 会议号
	meetingId, err = strconv.Atoi(r.PostFormValue("meetingId"))
	if err != nil {
		_, _ = w.Write(response.ParamErr.ToByte())
		return
	}

	// 获取会议
	meeting := meetingList.GetMeetingById(meetingId)
	if meeting == nil {
		_, _ = w.Write(response.MeetingNotExist.ToByte())
		return
	}

	// 将该用户添加到会议的用户列表中
	err = meeting.AddUser(model.NewUser(userId, model.Address(r.RemoteAddr)))
	if err != nil {
		_, _ = w.Write(response.MeetingFullErr.ToByte())
		return
	}

	// 将会议信息返回给用户
	res := response.Success
	res.WithData(meeting)
	_, _ = w.Write(res.ToByte())
}

type EndMeetingHandler struct{}

// 移除会议
// 需要的参数：端口，会议号
// 在移除会议前，需要检查该会议是否由承载该会议的服务器发出
func (handler *EndMeetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 获取对方服务器地址，该地址分为 ip 和端口两部分
	// 端口可能并不是提供服务的修改，所以需要修改
	addrX := strings.Split(r.RemoteAddr, ":")

	// 检查 ip 的正确性
	hostSplit := strings.Split(addrX[0], ".")
	if len(hostSplit) != 4 {
		_, _ = w.Write(response.IpErr.ToByte())
		return
	}

	// 从 r.Body 中读取数据
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// 解析读取到的数据
	var data map[string]interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// 获取端口号
	port := data["port"].(string)
	if port == "" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// 拼装地址
	addr := model.Address(addrX[0] + ":" + port)

	// 获取会议号
	meetingId := int(data["meetingId"].(float64))

	// 检查该请求是否由对应服务器发出
	if meetingList.GetMeetingById(meetingId).Addr != addr {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// 将该会议移除
	err = RemoveMeetingById(meetingId)
	if err != nil {
		fmt.Println(err.Error())
	}

	w.WriteHeader(http.StatusOK)
}

// 计算负载
func calculateLoad(pattern, limit int) int {
	switch pattern {
	case discussPattern:
		return limit * limit
	default:
		return math.MaxInt32
	}
}

// RemoveMeetingById 移除会议
func RemoveMeetingById(id int) error {
	// 获取会议
	meeting := meetingList.GetMeetingById(id)
	if meeting == nil {
		return errors.New("meeting does not exist")
	}

	// 获取会议负载
	load := calculateLoad(discussPattern, meeting.GetLimit())

	// 获取搭载这个会议的服务器
	server := serverList.GetServerByAddr(meeting.Addr)
	if server == nil {
		return errors.New("address error")
	}

	// 移除会议，并减少服务器负载
	meetingList.RemoveMeetingById(meeting.Id)
	server.MinusLoad(load)
	return nil
}
